package net.oschina.allchat.message.processing.json.parsers.error;

import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;

import net.oschina.allchat.message.processing.IProcessingFactory;
import net.oschina.allchat.message.processing.json.ParsingUtils;
import net.oschina.allchat.protocol.Constants;
import net.oschina.allchat.protocol.core.ProtocolException;
import net.oschina.allchat.protocol.core.stanza.Iq;
import net.oschina.allchat.protocol.core.stanza.Stanza;
import net.oschina.allchat.protocol.core.stanza.error.BadRequest;
import net.oschina.allchat.protocol.core.stanza.error.StanzaError;
import net.oschina.allchat.protocol.im.stanza.Message;
import net.oschina.allchat.protocol.im.stanza.Presence;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

public class StanzaErrorParser {
	public StanzaError parse(IProcessingFactory processingFactory, JsonParser jsonParser,
				Stanza stanza) throws JsonParseException, IOException {
		return doParse(processingFactory, jsonParser, stanza);
	}

	private StanzaError doParse(IProcessingFactory processingFactory, JsonParser jsonParser,
			Stanza stanza) throws JsonParseException, IOException {
		String senderJson = null, errorJson = null;
		JsonToken token = jsonParser.getCurrentToken();
		
		for (int i = 0; i < 2; i++) {
			long errorStart = jsonParser.getCurrentLocation().getCharOffset();
			String fieldName = jsonParser.getCurrentName();
			if (Constants.SYMBOL_FIELD_TEXT.equals(fieldName)) {
				senderJson = parseSenderJson(jsonParser);
			} else if ("_error".equals(fieldName)) {
				errorJson = parseErrorJson(jsonParser, errorStart);
			} else {
				throw ParsingUtils.newStanzaParsingException(jsonParser, String.format("element %s not be allowed here"));
			}
			
			token = jsonParser.nextToken();
			if (token == JsonToken.END_OBJECT)
				break;
			
			if (token != JsonToken.FIELD_NAME)
				throw ParsingUtils.newStanzaParsingException(jsonParser, "should be field name");
			
			token = jsonParser.nextToken();
		}
		
		Object object = processingFactory.parse(errorJson);
		if (!(object instanceof StanzaError)) {
			throw new ProtocolException(new BadRequest("not a valid stanza error"));
		}
		
		StanzaError error = (StanzaError)object;
		if (stanza instanceof Presence) {
			error.setKind(StanzaError.Kind.PRESENCE);
		} else if (stanza instanceof Message) {
			error.setKind(StanzaError.Kind.MESSAGE);
		} else if (stanza instanceof Iq) {
			error.setKind(StanzaError.Kind.IQ);
		} else {
			throw new ProtocolException(new BadRequest("invalid stanza kind"));
		}
		
		error.setFrom(stanza.getFrom());
		error.setTo(stanza.getTo());
		error.setId(stanza.getId());
		error.setLang(stanza.getLang());
		
		error.setSenderMessage(senderJson);
		
		return error;
	}

	private String parseErrorJson(JsonParser jsonParser, long errorStart) throws JsonParseException, IOException {
		if (jsonParser.getCurrentToken() != JsonToken.START_OBJECT)
			throw ParsingUtils.newStanzaParsingException(jsonParser, "should be start object");
		int level = 1;
		
		StringReader reader = (StringReader)jsonParser.getInputSource();
		String message = hackMessage(reader);
		
		while (level != 0) {
			JsonToken token = jsonParser.nextToken();
			if (token == JsonToken.START_OBJECT)
				level++;
			
			if (token == JsonToken.END_OBJECT)
				level--;
		}
		long errorEnd = jsonParser.getCurrentLocation().getCharOffset();
		
		return "{_error:" + message.substring((int)errorStart - 1, (int)errorEnd) + "}";
	}

	private String hackMessage(StringReader reader) {
		
		try {
			Field str = reader.getClass().getDeclaredField("str");
			boolean oldAccessible = str.isAccessible();
			str.setAccessible(true);
			try {
				return (String)str.get(reader);
			} catch (Exception e) {
				throw e;
			} finally {
				str.setAccessible(oldAccessible);
			}
		} catch (Exception e) {
			throw new RuntimeException("can't hack message");
		}
	}

	private String parseSenderJson(JsonParser jsonParser) throws JsonParseException, IOException {
		if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
			throw new ProtocolException(new BadRequest("should be value string"));
		}
		
		return jsonParser.getValueAsString();
	}
}
